import type { TableInstance } from 'element-plus'
import {
  ElCheckbox,
  ElCheckboxGroup,
  ElRadio,
  ElRadioGroup,
  ElTable,
  ElTableColumn,
} from 'element-plus'
import type { FunctionalComponent, VNodeChild } from 'vue'
import {
  ref,
  resolveDirective,
  withDirectives,
} from 'vue'
import { isFunction } from '@vueuse/core'
import Pagination from '@/components/Pagination'

interface TableBaseColumn<T> {
  title: string | ((column: TableBaseColumn<T>) => VNodeChild)
  key: string & keyof T
  type?: never
  render?: (rowData: T, rowIndex: number) => VNodeChild
  props?: InstanceType<typeof ElTableColumn>['$props']
}

interface TableNormalColumn<T> {
  title: string | ((column: TableBaseColumn<T>) => VNodeChild)
  key: string
  type?: never
  render?: (rowData: T, rowIndex: number) => VNodeChild
  props?: InstanceType<typeof ElTableColumn>['$props']
}

export type TableColumn<T> = TableBaseColumn<T> | TableNormalColumn<T>

function renderColumn<T>(tableColumn: TableColumn<T>) {
  return (
    <ElTableColumn
      prop={tableColumn.key}
      {...tableColumn.props}
    >
      {{
        header: () => isFunction(tableColumn.title) ? (tableColumn.title as Function)(tableColumn) : tableColumn.title,
        default: ({ row, $index }: { row: T; $index: number }) => row && tableColumn.render?.(row, $index),
      }}
    </ElTableColumn>
  )
}
export interface PaginationProps {
  total: number
  page: number
  limit: number
  pageSizes?: number
  hidden?: boolean
  onPagination: (params: { page: number; limit: number }) => Promise<void>
}

interface ICommonTableProps<T, Key = string> {
  columns: TableColumn<T>[]
  rowKey?: (row: T) => Key
  loading: boolean
  data: T[]
  pagination: PaginationProps
  onChange: (params: Key | Key[]) => void
}

interface INonSelectionTableProps<T, Key = string> extends ICommonTableProps<T, Key> {
  selection: undefined
  selections: undefined
}

interface ISingleTableProps<T, Key = string> extends ICommonTableProps<T, Key> {
  selections: Key
  selection: 'single'
  disabled?: boolean
}

interface IMultipleTableProps<T, Key = string> extends ICommonTableProps<T, Key> {
  selections: Key[]
  selection: 'multiple'
  disabled?: boolean
}

function switchSelection<T, Key>(selection: (ISingleTableProps<T, Key> | IMultipleTableProps<T, Key> | INonSelectionTableProps<T, Key>)['selection']) {
  switch (selection) {
    case 'multiple':
      return { Outer: ElCheckboxGroup, Selection: ElCheckbox }
    case 'single':
      return { Outer: ElRadioGroup, Selection: ElRadio }
    default:
      return {
        Outer: (props: any, { slots: { default: Component }, attrs }: { slots: { default: FunctionalComponent }; attrs: Record<string, any> }) => <div {...attrs}><Component /></div>,
        Selection: () => <></>,
      } as { Outer: any; Selection: FunctionalComponent<{ value: Key; disabled?: boolean }> }
  }
}

export default function<T, Key = string>(
  props: ISingleTableProps<T, Key> | IMultipleTableProps<T, Key> | INonSelectionTableProps<T, Key>,
) {
  const {
    columns,
    loading,
    data,
    pagination,
    selection,
    rowKey,
    onChange,
  } = props
  const tableRef = ref<TableInstance>()
  const loadingDirective = resolveDirective('loading')
  const { Outer, Selection } = switchSelection(selection)
  const selections = selection && props.selections
  return (
    <Outer modelValue={selections} onChange={onChange} class="flex flex-col flex-1 overflow-hidden w-full">
      {
        withDirectives(
          <ElTable
            ref={tableRef}
            class="flex-1 overflow-auto"
            height={0}
            data={data}
          >
            {
              selection
              && (
                <ElTableColumn width={55}>
                  {{
                    default: ({ row }: { row: T }) => <Selection disabled={props.disabled} value={rowKey?.(row)} />,
                  }}
                </ElTableColumn>
              )
            }
            {
              columns.map(renderColumn<T>)
            }
          </ElTable>,
          [[loadingDirective, loading]],
        )
      }
      {pagination && (pagination.total / pagination.limit) >= 1 && <Pagination {...pagination} />}
    </Outer>
  )
}
